/**
 * Copyright (c) 2011 Tomi Paananen.
 *
 * Part of Quick Gestures engine library.
 */

// Own header
#include "xmlparser.h"

// Qt includes
#include <QPoint>
#include <QString>
#include <QStringList>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>

// qgengine includes
#include "gesture.h"
#include "qgcommon.h"
#include "qgtrace.h"

// Constants
const QString QGElementNameGestures("gestures");
const QString QGElementNameGesture("gesture");
const QString QGAttributeNameId("id");
const QString QGAttributeNameName("name");
const QString QGAttributeNameAttributes("attributes");
const QString QGAttributeNamePoints("points");
const QString QGPointStringIntDelimiter(",");
const QString QGPointStringDelimiter(";");


/*!
  \class XmlParser
  \brief XML parser.
*/


/*!
  Constructor.
*/
XmlParser::XmlParser(QObject *parent /* = 0 */)
    : QObject(parent),
      mReader(0),
      mWriter(0)
{
}


/*!
  Destructor.
*/
XmlParser::~XmlParser()
{
    delete mReader;
    delete mWriter;
}


/*!
    Writes \a gestures into \a target. Returns XmlParser::NoError if success,
    an error code otherwise.
*/
int XmlParser::writeGestures(QIODevice *target, QList<Gesture*> &gestures)
{
    const int count(gestures.count());

    DEBUG_INFO("Gesture count is " << count);

    if (!target || !count) {
        DEBUG_INFO("No target or gestures given!");

        if (!target) {
            // No target!
            return QGEngine::InvalidIODevice;
        }

        // No gestures!
        return QGEngine::InvalidArgument;
    }

    if (!mWriter) {
        mWriter = new QXmlStreamWriter();
    }

    mWriter->setDevice(target);
    mWriter->setAutoFormatting(true);
    mWriter->writeStartDocument();
    mWriter->writeStartElement(QGElementNameGestures);

    // Write the gestures into the given target.
    int error(QGEngine::NoError);
    int latestError(QGEngine::NoError);

    for (int i = 0; i < count; ++i) {
        error = writeGesture(gestures[i]);

        emit progress((int)qRound(i * 100 / count));

        if (error != QGEngine::NoError) {
            // An error occured! Store the error code and try to continue
            // anyway.
            DEBUG_INFO("An error occured: " << error);
            latestError = error;
        }
    }

    mWriter->writeEndElement(); // QGElementNameGestures
    mWriter->writeEndDocument();
    emit progress(100);

    return latestError;
}


/*!
    Reads the data into \a gestures from \a source. Returns XmlParser::NoError
    if success, false otherwise.
*/
int XmlParser::readGestures(QIODevice *source, QList<Gesture*> &gestures)
{
    if (!source) {
        DEBUG_INFO("No source given!");
        return QGEngine::InvalidIODevice;
    }

    // Resolve the the total token count for reporting progress.
    const int totalTokenCount(tokenCount(source));
    int currentTokenCount(0);

    if (!mReader) {
        mReader = new QXmlStreamReader();
    }

    mReader->setDevice(source);
    QXmlStreamReader::TokenType type;
    QStringRef token;
    Gesture *gesture(0);
    bool expectingPoints(false);

    while (!mReader->atEnd()) {
        type = mReader->readNext();
        ++currentTokenCount;

        //DEBUG_INFO("Read token of type " << mReader->tokenString());

        switch (type) {
            case QXmlStreamReader::StartElement: {
                token = mReader->name();

                if (!token.compare(QGElementNameGesture) ||
                    !token.compare("pattern")) {
                    if (gesture) {
                        DEBUG_INFO("Nested gesture elements not allowed!");
                        return QGEngine::UnexpectedElement;
                    }

                    gesture = new Gesture();

                    // Parse the attributes for the gesture.
                    parseGestureAttributes(gesture, mReader->attributes());
                }
                else if (!token.compare(QGAttributeNamePoints)) {
                    if (gesture) {
                        // Expecting points of the gesture to come up with the
                        // next readNext().
                        expectingPoints = true;
                    }
                }

                break;
            }
            case QXmlStreamReader::EndElement: {
                token = mReader->name();

                if (!token.compare(QGElementNameGesture) ||
                    !token.compare("pattern")) {
                    // The end element was </gesture>.
                    if (gesture) {
                        // Add the gesture into the buffer.
                        gestures.append(gesture);
                        emit gestureParsed(gesture);
                        gesture = 0;
                    }
                }
                else if (!token.compare(QGAttributeNamePoints)) {
                    // Points should have been read.
                    expectingPoints = false;
                }

                break;
            }
            case QXmlStreamReader::Characters: {
                token = mReader->text();

                if (expectingPoints) {
                    // Parse the points from the token string and add them to
                    // the Gesture.
                    parseGesturePoints(gesture, token.toString());
                }

                break;
            }
            case QXmlStreamReader::Invalid: {
                DEBUG_INFO("Invalid XML type!");
                return QGEngine::InvalidType;
            }
            default: {
                DEBUG_INFO("Warning: Unhandled XML type:" << type);
                // Do nothing.
            }
        } // switch (type)

        //DEBUG_INFO("Read token content is " << token.toString().toLatin1());
        emit progress((int)qRound(currentTokenCount * 100 / totalTokenCount));
    }

    if (gesture) {
        DEBUG_INFO("A Gesture was left incomplete.");
        delete gesture;
        return QGEngine::IncompleteData;
    }

    if (gesture) {
        DEBUG_INFO("Parsed gesture:" << gesture->id() << gesture->name());
    }

    return QGEngine::NoError;
}


/*!
    Writes \a gesture using the current stream writer.
    Returns XmlParser::NoError if success, an error code otherwise.
*/
int XmlParser::writeGesture(Gesture *gesture)
{
    if (!mWriter || !mWriter->device()) {
        DEBUG_INFO("Invalid stream writer instance!");
        return QGEngine::InvalidIODevice;
    }

    if (!gesture) {
        DEBUG_INFO("The given gesture is NULL!");
        return QGEngine::InvalidArgument;
    }

    mWriter->writeStartElement(QGElementNameGesture);
    mWriter->writeAttribute(QGAttributeNameId, QString::number(gesture->id()));
    mWriter->writeAttribute(QGAttributeNameName, gesture->name());
    mWriter->writeAttribute(QGAttributeNameAttributes,
                            QString::number(gesture->attributes()));
    mWriter->writeStartElement(QGAttributeNamePoints);

    QString pointString;

    const PointList points = gesture->points();
    const int pointCount(points.count());
    QPoint currentPoint;

    for (int i = 0; i < pointCount; ++i) {
        currentPoint = points[i];
        pointString = QString::number(currentPoint.x()) +
                      QGPointStringIntDelimiter + " " +
                      QString::number(currentPoint.y());

        if (i < pointCount - 1) {
            pointString += QGPointStringDelimiter + " ";
        }

        mWriter->writeCharacters(pointString);
    }

    mWriter->writeEndElement(); // points
    mWriter->writeEndElement(); // gesture

    return QGEngine::NoError;
}


/*!
    Parses the gesture points from \a pointString. The points are appended
    into \a gesture.
*/
void XmlParser::parseGesturePoints(Gesture *gesture, QString pointsString) const
{
    if (!gesture || pointsString.isEmpty()) {
        DEBUG_INFO("No gesture given or the point string is empty!");
        return;
    }

    QStringList pointStrings = pointsString.split(QGPointStringDelimiter);
    QString pointString;
    QStringList pointCoords;
    QPoint point;
    PointList points;

    foreach (pointString, pointStrings) {
        QStringList pointCoords = pointString.split(QGPointStringIntDelimiter);

        if (pointCoords.count() == 2) {
            point.setX(pointCoords[0].toInt());
            point.setY(pointCoords[1].toInt());
            points.append(point);
        }
        else {
            DEBUG_INFO("Failed to parse a point from the string!");
        }
    }

    gesture->setPoints(points);
}


/*!
    Parses \a attributes for \a gesture.
*/
void XmlParser::parseGestureAttributes(Gesture *gesture,
                                       QXmlStreamAttributes attributes) const
{
    if (!gesture) {
        DEBUG_INFO("No gesture given!");
        return;
    }

    QStringRef attributeValue;
    QString valueAsString;

    if (attributes.hasAttribute(QGAttributeNameId)) {
        attributeValue = attributes.value(QGAttributeNameId);
        valueAsString = attributeValue.toString();
        gesture->setId(valueAsString.toUInt());
        //DEBUG_INFO("Parsed gesture ID:" << gesture->id());
    }

    if (attributes.hasAttribute(QGAttributeNameName)) {
        attributeValue = attributes.value(QGAttributeNameName);
        valueAsString = attributeValue.toString();
        gesture->setName(valueAsString);
        //DEBUG_INFO("Parsed gesture name:" << valueAsString);
    }

    if (attributes.hasAttribute(QGAttributeNameAttributes) ||
        attributes.hasAttribute("attrs")) {
        if (attributes.hasAttribute(QGAttributeNameAttributes))
            attributeValue = attributes.value(QGAttributeNameAttributes);
        else
            attributeValue = attributes.value("attrs");

        valueAsString = attributeValue.toString();
        gesture->setAttributes(valueAsString.toULongLong());
        //DEBUG_INFO("Parsed gesture attributes:" << gesture->attributes());
    }
}


/*!
  Returns the total count of tokens in \a source.
*/
int XmlParser::tokenCount(QIODevice *source)
{
    if (!mReader) {
        mReader = new QXmlStreamReader();
    }

    mReader->setDevice(source);
    int count(0);

    while (!mReader->atEnd()) {
        mReader->readNext();
        ++count;
    }

    if (!source->reset()) {
        DEBUG_INFO("FAILED TO RESET THE QIODEVICE!");
    }

    return count;
}


// End of file.
